package com.study.springsecurity.config.security;

import com.study.springsecurity.constant.CommonConstant.PageUrl;
import com.study.springsecurity.constant.CommonConstant.Role;
import com.study.springsecurity.filter.VerificationCodeFilter;
import com.study.springsecurity.handler.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.web.cors.CorsConfigurationSource;

@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Autowired
    private SessionRegistry sessionRegistry;

    @Autowired
    private CorsConfigurationSource corsConfigurationSource;

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                .antMatchers("/static/**"); // 允许静态资源不经过认证 TODO 未生效，待排查
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 异常情况处理
        http.exceptionHandling((exceptionHandling) ->
                exceptionHandling
                        .accessDeniedHandler(new MyAccessDeniedHandler()) // 设置没有权限访问后续处理
                        .authenticationEntryPoint(new NotLoginAuthenticationEntryPoint()) // 设置用户未登录时访问需要登录的接口处理逻辑
        );

        // 表单登录
        http.formLogin()
                // 登录时传递用户名和密码使用的参数名
                .usernameParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY)
                .passwordParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY)

                // 修改默认的登录页为login.html，他会自动去根路径static文件夹下寻找login.html
                .loginPage(PageUrl.LOGIN_PAGE)

                // 设置登录接口地址，这个接口不是真实存在的，还是用的security给我们提供的，之所以要有这个配置，是login.html当中form表单提交的地址我们设置的是这个
                .loginProcessingUrl(PageUrl.LOGIN_URL)

                .successHandler(new MyAuthenticationSuccessHandler()) // 登录成功之后的处理器

                .failureHandler(new MyAuthenticationFailureHandler()) // 登录失败之后的处理器

                .permitAll(); // permitAll中文意思是许可所有的：所有的都遵循上面的配置的意思

        // 接口权限认证
        http.authorizeRequests()
                // 该路由不需要身份认证
                .antMatchers(PageUrl.NO_NEED_PERMIT_ARR).permitAll()

                // 对于登录接口允许匿名访问
                .antMatchers(PageUrl.ANONYMOUS_URL_MATCHER).anonymous()

                // 只要登录就能访问
                .antMatchers(PageUrl.ONLY_NEED_AUTH_ARR).authenticated()

                // Admin用户可以访问任意接口
                // H2数据库相关接口需要用户属于H2角色
                .antMatchers(PageUrl.H2_URL_MATCHER).hasAnyRole(Role.H2_ROLE)
                // 用户信息接口需要用户属于普通用户角色
                .antMatchers(PageUrl.USERINFO_URL_MATCHER).hasAnyRole(Role.PLAT_USER_ROLE)
                // 平台管理接口需要用户带有平台管理者的角色
                .antMatchers(PageUrl.PLAT_MANAGE_URL_MATCHER).hasAnyRole(Role.PLAT_MANAGE_ROLE)

                // 接口只用同时拥有ROLE_ADMIN和ROLE_USER角色才能访问
                //.antMatchers("/find").access("hasAuthority('ROLE_ADMIN') and hasAuthority('ROLE_USER')")

                // 需要用户具备这个接口的权限
                //.antMatchers("/find").hasAuthority("xxxxxx")

                // 未被前面antMatchers匹配的任何请求都只需要认证就可以访问
                //.anyRequest().authenticated();

                // 未被前面antMatchers匹配的任何请求都限制访问
                .anyRequest().denyAll();

        // 退出,这里的/login/loginOut的请求是和前端的接口约定，是security给我们提供的,退出成功后跳转登录页/login.html 或者 执行Handler
        http.logout().logoutUrl(PageUrl.LOGIN_OUT_URL)
                .logoutSuccessHandler(new MyLogoutSuccessHandler())
                //.logoutSuccessUrl(PageUrl.LOGIN_PAGE)
                .permitAll();


        http.rememberMe() // 设置记住我
                .tokenRepository(persistentTokenRepository) // 配置token存储库
                // 设置有效时长180秒，默认 2 周时间。
                .tokenValiditySeconds(180)
                .userDetailsService(userDetailsService);

        http.sessionManagement() // 回话管理器
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 指定session创建策略

                .maximumSessions(1) // 同一个用户的最大会话数（同一个账号允许同时登录多个客户端的数量）
                .maxSessionsPreventsLogin(false) // 达到回话限制时的处理，true：当前会话无法登录成功  false：之前的会话失效
                .sessionRegistry(sessionRegistry); // 指定session注册器

        // 关闭 csrf
        http.csrf().disable();

        // https://blog.csdn.net/fd2025/article/details/124491570
        http.headers().frameOptions().sameOrigin(); // 页面只能被本站页面嵌入到iframe或者frame中

        http.cors().configurationSource(corsConfigurationSource);

        // 在用户密码认证Filter之前新增验证码检查的Filter
        http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

}
   